package cn.edu.neu.vmmigeval.cloud.policy.mgrdsm;


import cn.edu.neu.vmmigeval.cloud.config.Migration;
import cn.edu.neu.vmmigeval.cloud.entity.Host;
import cn.edu.neu.vmmigeval.cloud.entity.Vm;
import cn.edu.neu.vmmigeval.cloud.policy.MigratedHostPolicy;
import cn.edu.neu.vmmigeval.cloud.policy.VmPlacementPolicy;
import cn.edu.neu.vmmigeval.evaluation.PowerModel;

import java.util.*;
import java.util.Map.Entry;

public class VmPlacementPolicyMGSM extends VmPlacementPolicy {

	public VmPlacementPolicyMGSM(List<Host> hostList, MigratedHostPolicy migratedHostPolicy){
		super(hostList,migratedHostPolicy);
	}

	public VmPlacementPolicyMGSM(MigratedHostPolicy migratedHostPolicy){
		super(migratedHostPolicy);
	}


	public VmPlacementPolicyMGSM(){

	}
	public List<Migration> getNewVmPlacement(List<Vm> vmsToMigrate, List<Host> hosts) {
		List<Migration> migrationMap = new LinkedList<Migration>();
		//sortVmByDecreasingUtilization(vmsToMigrate);
		List<Host> hs = getExcludedHosts(hosts);

		//for (Vm vm : vmsToMigrate) {
		for (int i = 0; i < vmsToMigrate.size(); i++) {
			Vm vm = vmsToMigrate.get(i);
			Host targetHost = findHostForVm(vm, hs);
			if (targetHost != null) {

				Host srcHost = vm.getHost();
				vm.setInMigration(true);
				List<Vm> vms = new ArrayList<Vm>();
				vms.add(vm);
				Migration migration = new Migration();
				migration.setSrcHost(srcHost);
				migration.setDestHost(targetHost);
				migration.setVms(vms);
				//		System.out.println("�����:"+vm.getId()+" ��"+srcHost.getId()+"Ǩ�Ƶ�"+targetHost.getId());
				//migration.setClocks((int)((vm.getCurrentRam()/(targetHost.getTotalBw()-targetHost.getCurrentBw()+10)))+1);
				//����Ǩ�Ƶ�ʱ������Ϊ1����Ϊ��ģ���ʱ����һ����λΪ10��
				migration.setClocks(1);
				//	System.out.println("clocks is :"+migration.getClocks());
				migration.setEnd(false);

				srcHost.setMigration(true);
				targetHost.setMigration(true);
				targetHost.setActive(0);
				migrationMap.add(migration);
			}
		}
		return migrationMap;
	}
	public List<Double> getFourFactorsLoadBalance(){
		double cpuAverage=0,ramAverage=0,bwAverage=0,diskAverage=0;
		double cpuSum=0,ramSum=0,bwSum=0,diskSum=0;
		int num= getHostList().size();
		for (int i = 0; i < getHostList().size(); i++) {
			cpuSum += getHostList().get(i).getCurrentCpu()/getHostList().get(i).getTotalCpu();
			ramSum += getHostList().get(i).getCurrentRam()/getHostList().get(i).getTotalRam();
			bwSum += getHostList().get(i).getCurrentBw()/getHostList().get(i).getTotalBw();
			diskSum += getHostList().get(i).getCurrentDisk()/getHostList().get(i).getTotalDisk();
		}
		cpuAverage = cpuSum /num;
		ramAverage = ramSum /num;
		bwAverage = bwSum /num;
		diskAverage = diskSum /num;
		List<Double> result = new ArrayList<Double>();
		result.add(cpuAverage);
		result.add(ramAverage);
		result.add(bwAverage);
		result.add(diskAverage);
		return result;
	}
	public double getLoadBalance(List<Host> hosts){
		double cpuAverage=0,ramAverage=0,bwAverage=0,diskAverage=0;
		double cpuSum=0,ramSum=0,bwSum=0,diskSum=0;
		int num= getHostList().size();
		for (int i = 0; i < getHostList().size(); i++) {
			cpuSum += getHostList().get(i).getCurrentCpu()/getHostList().get(i).getTotalCpu();
			ramSum += getHostList().get(i).getCurrentRam()/getHostList().get(i).getTotalRam();
			bwSum += getHostList().get(i).getCurrentBw()/getHostList().get(i).getTotalBw();
			diskSum += getHostList().get(i).getCurrentDisk()/getHostList().get(i).getTotalDisk();
		}
		cpuAverage = cpuSum /num;
		ramAverage = ramSum /num;
		bwAverage = bwSum /num;
		diskAverage = diskSum /num;
		double sum1 = 0;
		for (int i = 0; i < getHostList().size(); i++) {
			double cpu1 = getHostList().get(i).getCurrentCpu()/getHostList().get(i).getTotalCpu() - cpuAverage;
			double ram1 = getHostList().get(i).getCurrentRam()/getHostList().get(i).getTotalRam()-ramAverage;
			double bw1 = getHostList().get(i).getCurrentBw()/getHostList().get(i).getTotalBw()-bwAverage;
			double disk1 = getHostList().get(i).getCurrentDisk()/getHostList().get(i).getTotalDisk()-diskAverage;
			double total = cpu1*cpu1+ram1*ram1+bw1*bw1+disk1*disk1;
			double totali = Math.sqrt(total);
			sum1+=totali;
		}
		return sum1/num;

	}
	/**
	 * @author zzh
	 * @param vmList
	 * ����CPU�����ʶ�������б���н�������
	 */
	private void sortVmByDecreasingUtilization(List<Vm> vmList) {
		Collections.sort(vmList, new Comparator<Vm>() {
			@Override
			public int compare(Vm a, Vm b) throws ClassCastException {
				Double aUtilization =  (double)a.getCurrentCpu()/a.getTotalCpu();
				Double bUtilization = (double)b.getCurrentCpu()/b.getTotalCpu();
				return bUtilization.compareTo(aUtilization);
			}
		});
	}

	/**
	 * Find host for vm.
	 *
	 * @param vm the vm
	 * @param excludedHosts the excluded hosts
	 * @return the power host
	 */
	public Host findHostForVm(Vm vm, List<Host> excludedHosts) {
		double minPower = Double.MAX_VALUE;
		Host allocatedHost = null;
		//PowerModel pm = null;
		Map<Host,Double> map = new HashMap<Host, Double>();
		List<Double> balance = getFourFactorsLoadBalance();
		for (Host host : excludedHosts) {
			if (host.isSuitableForVm(vm)) {
				if (getUtilizationOfCpuMips(host) != 0 && isHostOverUtilizedAfterAllocation(host, vm)) {
					continue;
				}
				double totalCpu = (vm.getCurrentCpu()+host.getCurrentCpu())/host.getTotalCpu();
				double totalRam = (vm.getCurrentRam()+host.getCurrentRam())/host.getTotalRam();
				double totalBw = (vm.getCurrentBw()+host.getCurrentBw())/host.getTotalBw();
				double totalDisk = (vm.getCurrentDisk()+host.getCurrentDisk())/host.getTotalDisk();

				double cha = (balance.get(0)-totalCpu)*0.4+(balance.get(1)-totalRam)
						+(balance.get(2)-totalBw)+(balance.get(3)-totalDisk);
				map.put(host, cha);
			}
		}
		double min = 10000;
		for (Entry<Host,Double> entry : map.entrySet()) {
			if(entry.getValue()<min){
				min = entry.getValue();
				allocatedHost = entry.getKey();
			}
		}
		return allocatedHost;
	}

	/**
	 * Checks if is host over utilized after allocation.
	 *
	 * @param host the host
	 * @param vm the vm
	 * @return true, if is host over utilized after allocation
	 */
	/*protected boolean isHostOverUtilizedAfterAllocation(Host host, Vm vm) {
		boolean isHostOverUtilizedAfterAllocation = true;
		Host host2 = vm.getHost();
		if (host.vmCreate(vm)) {
			isHostOverUtilizedAfterAllocation = this.getMigratedHostPolicy().isHostOverUtilized(host);
			host.vmDestroy(vm);
			vm.setHost(host2);
		}
		return isHostOverUtilizedAfterAllocation;
	}*/

	/**
	 * Gets the utilization of the CPU in MIPS for the current potentially allocated VMs.
	 *
	 * @param host the host
	 *
	 * @return the utilization of the CPU in MIPS
	 */
	protected double getUtilizationOfCpuMips(Host host) {
		double hostUtilizationMips = 0;
		for (Vm vm2 : host.getVmList()) {
			if (host.getVmsMigratingIn().contains(vm2)) {
				// calculate additional potential CPU usage of a migrating in Broker
				//	hostUtilizationMips += host.getTotalAllocatedMipsForVm(vm2) * 0.9 / 0.1;
				hostUtilizationMips += vm2.getTotalCpu() * 0.9 / 0.1;
			}
			//	hostUtilizationMips += host.getTotalAllocatedMipsForVm(vm2);
			hostUtilizationMips += vm2.getTotalCpu();
		}
		return hostUtilizationMips;
	}

	public double getPowerAfterAllocation(Host host,Vm vm){


		double cpu = (double)(host.getCurrentCpu()+vm.getCurrentCpu())/host.getTotalCpu();
		double ram = (double)(host.getCurrentRam()+vm.getCurrentRam())/host.getTotalRam();
		double bw = (double)(host.getCurrentBw()+vm.getCurrentBw())/host.getTotalBw();
		double disk = (double)(host.getCurrentDisk()+vm.getCurrentDisk())/host.getTotalDisk();

		return PowerModel.getPowerOfHostByCpuOR(cpu, ram, bw, disk);


	}




}
